package com.se.http;

import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.*;

/**
 * @author hdu_huang
 * @date 2021/8/16 21:41.
 * 基于Apache HttpClient的HttpClient
 */

@Slf4j
public class HttpApacheClient {

    private static final int RETRY_COUNT = 1;  //重试次数
    private static final int MAX_TOTAL_POOL = 120;// 连接池最大连接数
    private static final int MAX_CONN_PER_ROUTE = 20; // 每台主机(port)最大连接数
    private static final int CONN_REQUEST_TIMEOUT = 5 * 1000;// 从管理器获取连接超时时间

    public static final int CONNECT_TIMEOUT_MILLIS = Integer.getInteger("simple.apache.http.connect.timeout", 3000);// 等待创建连接超时时间
    public static final int READ_TIMEOUT_MILLIS = Integer.getInteger("simple.apache.http.read.timeout", 5000);// 连接后等待接收数据超时时间

    public static final String DEFAULT_CHARSET = StandardCharsets.UTF_8.name();

    //线程安全, 维持单实例即可
    private static final CloseableHttpClient httpClient;

    static {
        try {

            // 微信或其他证书
            char[] password = "1274201501".toCharArray();
            InputStream inputStream = HttpApacheClient.class.getClassLoader().getResourceAsStream("apiclient_cert.p12");
            KeyStore keystore = KeyStore.getInstance("PKCS12");
            keystore.load(inputStream, password);

            if (inputStream != null) {
                inputStream.close();
            }

            //忽略掉对服务器端证书的校验[信任所有]   TrustSelfSignedStrategy 信任系统证书  loadKeyMaterial加载证书
            TrustStrategy strategy = (chain, authType) -> true;
            //信任所有host
            HostnameVerifier verifier = (s, sslSession) -> true;

            SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(keystore, strategy).loadKeyMaterial(keystore, password).build();

            //信任系统证书是 SSLConnectionSocketFactory.getSystemSocketFactory()
            SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", socketFactory).build();

            // 初始化连接管理器
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            connectionManager.setMaxTotal(MAX_TOTAL_POOL);
            connectionManager.setDefaultMaxPerRoute(MAX_CONN_PER_ROUTE);
            RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONN_REQUEST_TIMEOUT)
                    .setConnectTimeout(CONNECT_TIMEOUT_MILLIS)
                    .setSocketTimeout(READ_TIMEOUT_MILLIS).build();

            //设置HttpClient
            HttpClientBuilder httpBuilder = HttpClients.custom().setConnectionManager(connectionManager).setDefaultRequestConfig(requestConfig)
                    //设置重试次数[不重试非幂等请求(POST)]
                    .setRetryHandler(new DefaultHttpRequestRetryHandler(RETRY_COUNT, false));

            httpClient = httpBuilder.build();


        } catch (Exception exception) {

            log.error(exception.getMessage(), exception);
            throw new IllegalStateException("HttpApacheClient init error!!!");
        }
    }

    public static HttpResult get(String url, Map<String, String> params) {
        HashMap<String, String> headers = builderHeaders();
        return get(url, headers, params);
    }

    public static HttpResult get(String url, Map<String, String> headers, Map<String, String> params) {
        return execute(buildHttpUriRequest(url, headers, params, null, "GET"), headers, params);
    }

    public static HttpResult postForm(String url, Map<String, String> headers, Map<String, String> params) {
        return execute(buildHttpUriRequest(url, headers, params, null, "POST"), headers, params);
    }

    public static HttpResult postJson(String url, Map<String, String> headers, String params) {
        return execute(buildHttpUriRequest(url, headers, null, params, "POST"), headers, params);
    }

    public static HttpResult postXml(String url, Map<String, String> headers, String params) {

        if (headers == null) {
            headers = builderHeaders();
        }

        headers.put("Content-Type", "application/xml"); //xml
        return execute(buildHttpUriRequest(url, headers, null, params, "POST"), headers, params);
    }



    public static HashMap<String, String> builderHeaders() {
        HashMap<String, String> headers = new HashMap<>();
        //headers.put("Accept-Encoding", "gzip,deflate,sdch");
        headers.put("Accept-Charset", DEFAULT_CHARSET);
        headers.put("Connection", "Keep-Alive");
        headers.put("RequestId", UUID.randomUUID().toString());
        headers.put("Request-Module", "Apache-HttpClient");

        return headers;
    }

    @SneakyThrows
    private static HttpUriRequest buildHttpUriRequest(@NonNull String url, Map<String, String> headers, Map<String, String> fromParams, String params, @NonNull String method) {

        HttpUriRequest request = null;

        if (headers == null) {
            headers = builderHeaders();
        }

        switch (method) {

            case HttpMethod.GET:

                String encodedContent = HttpV1Client.encodingParams(fromParams, DEFAULT_CHARSET);
                if (!encodedContent.isBlank()) {
                    url += ("?" + encodedContent);
                }
                request = new HttpGet(url);

                break;
            case HttpMethod.POST:

                HttpPost httpPost = new HttpPost(url);
                request = httpPost;

                if (params == null) { // from

                    if (fromParams == null || fromParams.isEmpty()) {
                        break;
                    }

                    List<NameValuePair> parameters = new ArrayList<>(fromParams.size());

                    fromParams.forEach((key, value) -> {
                        parameters.add(new BasicNameValuePair(key, value));
                    });


                    UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(parameters, DEFAULT_CHARSET);
                    httpPost.setEntity(urlEncodedFormEntity);

                } else { // raw

                    StringEntity stringEntity = new StringEntity(params, ContentType.APPLICATION_JSON); //中文格式化
                    httpPost.setEntity(stringEntity);
                }

                break;
            case HttpMethod.PUT:
                request = new HttpPut(url);
                break;
            case HttpMethod.DELETE:
                request = new HttpDelete(url);
                break;
            default:
                request = new HttpGet(url);
                break;
        }

        headers.forEach(request::setHeader);
        return request;

    }


    //requestHeaders和params仅用于日志
    private static HttpResult execute(HttpUriRequest request, Map<String, String> requestHeaders, Object params) {

        log.debug("HttpApacheClient execute, request:{}, requestHeaders:{}, params:{}", request, requestHeaders, params);

        CloseableHttpResponse response = null;
        HttpEntity entity = null;

        try {
            response = httpClient.execute(request);
            // 获取响应状态码
            int status = response.getStatusLine().getStatusCode();
            entity = response.getEntity();
            String result = entity != null ? EntityUtils.toString(entity, DEFAULT_CHARSET) : null;

            HashMap<String, String> headers = new HashMap<>();
            for (Header header : response.getAllHeaders()) {
                headers.put(header.getName(), header.getValue());
            }

            if (status == 200) {
                //do something callback
            }

            return new HttpResult(status, result, headers);

        } catch (Exception e) {

            log.error("HttpApacheClient execute error! request:{}, requestHeaders:{}, params:{}, error:", request, requestHeaders, params, e);
            return new HttpResult(500, e.toString(), Collections.emptyMap());

        } finally { //return 不可达

            try {
                EntityUtils.consume(entity);//确保消耗完
                if (response != null) {
                    response.close();
                }
            } catch (Exception e) {
                log.error("HttpApacheClient execute error!!! request:{}, requestHeaders:{}, params:{}, error:", request, requestHeaders, params, e);
            }
        }
    }

}
